/*
 Jeffrey Klarfeld (je542304)
 William Greenburg (wi328885)
 
 Assignment #2
 
 */

#include "scanner.h"

/********Constant Values********/
#define MAX_NUM_LENGTH 5
#define MAX_ID_LENGTH 11
#define MAX_PROGRAM_LENGTH 3000

 /********Error Checks Required********/
/*
  –Variable does not start with letter

  –Number.length > MAX_NUM_LENGTH

  –Name.length > MAX_ID_LENGTH

  –Invalid Symbols
*/
/********Suggested symbols Enumeration*******/
/*typedef enum 
{
  nulsym = 1, identsym, numbersym, plussym, minussym,
  multsym,  slashsym, oddsym, eqsym, neqsym, lessym, leqsym,
  gtrsym, geqsym, lparentsym, rparentsym, commasym, semicolonsym,
  periodsym, becomessym, beginsym, endsym, ifsym, thensym,
  whilesym, dosym, callsym, constsym, intsym, procsym, outsym,
  insym , elsesym
} token_type;*/

enum token{nulsym = 1, identsym = 2, numbersym = 3, plussym = 4, minussym = 5, multsym = 6,
slashsym = 7, oddsym = 8, eqlsym = 9, neqsym = 10, lessym = 11, leqsym = 12,
gtrsym = 13, geqsym = 14, lparentsym = 15, rparentsym = 16, commasym = 17,
semicolonsym = 18, periodsym = 19, becomessym = 20, beginsym = 21, endsym = 22,
ifsym = 23, thensym = 24, whilesym = 25, dosym = 26, callsym = 27, constsym = 28,
intsym = 29, procsym = 30, outsym = 31, insym = 32, elsesym = 33};


/********Function Prototypes********/

// Store program data in symbols, and print program verbatim
void readAndPrintFile();


// Error Checking
int errorChecking(int *list, int index);

// Given program data, split into tokens and return symbols table
int tokenize(char* symbols, int index);

void symbolChecking(char* string);

// Returns the lexical value of a 2-char operator, or 0 if unrecognized
int digramLexicalValue(char* digram);

// Likewise, but for 1-char operators
int unigramLexicalValue(char unigram);

// aaaaand for them words, too
int alphnumLexicalValue(char* alphnum);

// Stores all non-comment, non-tab and non-null symbols in symbolsSansComments
void removeComments();

/********Global Variables [EEEK]********/
FILE *input;
FILE *output;
char inputFileName[MAX_PROGRAM_LENGTH];
char outputFileName[MAX_PROGRAM_LENGTH];
char symbols[MAX_PROGRAM_LENGTH][MAX_PROGRAM_LENGTH];
char symbolsSansComments[MAX_PROGRAM_LENGTH][MAX_PROGRAM_LENGTH];
int lexicalValues[MAX_PROGRAM_LENGTH];
int lexicalValuesSansComments[MAX_PROGRAM_LENGTH];
int currentSymbol=0;
int totalSymbolsSansComments=0;

/* Doesn't look like these are used anywhere but reading in...
   that "printf("%s",fgets(line,LEN,ifp));" looks pretty dirty.*/
char str[MAX_PROGRAM_LENGTH];


int scan_file(FILE* inPtr, FILE* outPtr, char* lexListStrOut)
{
    //input read from Command Line arguments
    
    // the strcpy into the filenames may not be strictly neccessary,
    // we'd only need that if we were to fopen from another function.
    // So we could put them back the way they were.

    input = inPtr;
    output = outPtr;

    readAndPrintFile();
    removeComments();

    fprintf(output,"\nLexeme Table:\n");
    fprintf(output,"\nLexeme     Token Type\n");
    int i; 
    for(i=0; i<totalSymbolsSansComments; i++)
    {
      fprintf(output,"%-10s %-4d\n", symbolsSansComments[i], lexicalValuesSansComments[i]);
    }

    /*  the goods -- return these in an array of sorts  */
    char bufferString[MAX_PROGRAM_LENGTH];
    int len=0;
    fprintf(output, "\nLexeme List:\n");
    for(i=0; i<totalSymbolsSansComments; i++){
      if(lexicalValuesSansComments[i] == 2){
        fprintf(output, "2 %s ", symbolsSansComments[i]);
        len += sprintf(bufferString+len, "2 %s ", symbolsSansComments[i]);
      }
      else if(lexicalValuesSansComments[i] == 3){
        fprintf(output, "3 %s ", symbolsSansComments[i]);
        len += sprintf(bufferString+len, "3 %s ", symbolsSansComments[i]);
      }
      else{
        fprintf(output, "%d ", lexicalValuesSansComments[i]);
        len += sprintf(bufferString+len, "%d ", lexicalValuesSansComments[i]);
      }
    }

    //copy the lexeme list to the caller's given string
    strcpy(lexListStrOut, bufferString);

    /****LAST THING BEFORE RETURN****/
    fclose(input);
    fclose(output);

    return 0;
}

void removeComments(){
  int i, j;
  j = 0;
  totalSymbolsSansComments = currentSymbol;
  for(i=0; i<currentSymbol; i++){
    //If we've come across an outermost "/*", advance i until a matching "*/"
    if(lexicalValues[i] == -1){
      while(lexicalValues[i] != -2){
        i++;
        totalSymbolsSansComments--;
      }
      i++;
      totalSymbolsSansComments--;
    }
    strcpy(symbolsSansComments[j], symbols[i]);
    lexicalValuesSansComments[j] = lexicalValues[i];
    j++;
  }
}

void symbolChecking(char* string)
{
  int i, j; 

  for(i=0; string[i] != '\0';)
  {
    if(isalpha(string[i]) || isdigit(string[i]))
    {
      for(j=0; isalpha(string[i]) || isdigit(string[i]); j++)
      {
        if(j > MAX_NUM_LENGTH && isdigit(string[0]))
        {
          //bork
          err(1, "ERROR - symbolChecking, Numerical value longer than MAX_NUM_LENGTH: %s", string);
        }
        if(j > MAX_ID_LENGTH)
        {
          err(1, "ERROR - symbolChecking, Identifier value longer than MAX_ID_LENGTH: %s", string);
        }
        symbols[currentSymbol][j] = string[i];
        i++;
      }
      //assign lexical value to word/number
      lexicalValues[currentSymbol] = alphnumLexicalValue(symbols[currentSymbol]);
    }
    else if(ispunct(string[i]))
    {
      char digram[3] = {string[i], string[i+1], '\0'};
      if(digramLexicalValue(digram) != 0)
      {
        strcpy(symbols[currentSymbol], digram);
        lexicalValues[currentSymbol] = digramLexicalValue(digram);
        i += 2;
      }
      else if(unigramLexicalValue(string[i]) != 0){
        symbols[currentSymbol][0] = string[i];
        lexicalValues[currentSymbol] = unigramLexicalValue(string[i]);
        i++;
      }
    }
    //else fprintf(input,"ERRORERRORERROR");
    else err(1, "ERROR - symbolChecking, Invalid Symbols: %s", string);
    currentSymbol++;
  }

  return;
}

int unigramLexicalValue(char unigram){
  if(unigram == ';') return semicolonsym;
  else if(unigram == ',') return commasym;
  else if(unigram == '.') return periodsym;
  else if(unigram == '+') return plussym;
  else if(unigram == '-') return minussym;
  else if(unigram == '=') return eqlsym;
  else if(unigram == '*') return multsym;
  else if(unigram == '/') return slashsym;
  else if(unigram == '.') return periodsym;
  else if(unigram == '>') return gtrsym;
  else if(unigram == '<') return lessym;
  else if(unigram == '(') return lparentsym;
  else if(unigram == ')') return rparentsym;
  //else return 0;
  else err(1, "ERROR - unigramLexicalValue, Invalid Char: %c", unigram);
}

int digramLexicalValue(char* digram){
  if(strcmp(digram, "/*") == 0) return -1; //indicates beginning of comment
  else if(strcmp(digram, "*/") == 0) return -2; //indicates end of comment
  else if(strcmp(digram, "==") == 0) return eqlsym;
  else if(strcmp(digram, "<=") == 0) return leqsym;
  else if(strcmp(digram, ">=") == 0) return geqsym;
  else if(strcmp(digram, "<>") == 0) return neqsym;
  else if(strcmp(digram, ":=") == 0) return becomessym;
  else return 0;
}

int alphnumLexicalValue(char* alphnum){
  if(strcmp(alphnum, "const") == 0) return constsym;
  else if(strcmp(alphnum, "procedure") == 0) return procsym;
  else if(strcmp(alphnum, "int") == 0) return intsym;
  else if(strcmp(alphnum, "call") == 0) return callsym;
  else if(strcmp(alphnum, "begin") == 0) return beginsym;
  else if(strcmp(alphnum, "null") == 0) return nulsym;
  else if(strcmp(alphnum, "end") == 0) return endsym;
  else if(strcmp(alphnum, "if") == 0) return ifsym;
  else if(strcmp(alphnum, "then") == 0) return thensym;
  else if(strcmp(alphnum, "else") == 0) return elsesym;
  else if(strcmp(alphnum, "while") == 0) return whilesym;
  else if(strcmp(alphnum, "odd") == 0) return oddsym;
  else if(strcmp(alphnum, "do") == 0) return dosym;
  else if(strcmp(alphnum, "in") == 0) return intsym;
  else if(strcmp(alphnum, "out") == 0) return outsym;
  else if(isdigit(alphnum[0])){
    int i;
    for(i=0; isdigit(alphnum[i]); i++);
    if(alphnum[i] == '\0') return numbersym;
    else return identsym;
  }
  else if(isalpha(alphnum[0])){
    int i;
    for(i=0; isalpha(alphnum[i]); i++);
    if(alphnum[i] == '\0') return identsym;
    else return numbersym;
  }
  else err(1, "ERROR - alphnumLexicalValue, Numerical value contains letter: %c", *alphnum);
}

void readAndPrintFile()
{
  fprintf(output, "Source Program:\n\n");

  //Prints last line twice. Need to fix that

  //Print the program verbatim, line by line
  while(!feof(input) && !ferror(input))
  {
    char thisLine[MAX_PROGRAM_LENGTH];
    if(fgets(thisLine,MAX_PROGRAM_LENGTH,input) != NULL)
    {
      fprintf(output, "%s", thisLine);
      //printf("%s", thisLine);
    }
  }
  //set file pointer back to the beginning
  rewind(input);
  //go through program string by string, and send each to symbolChecking
  char thisToken[MAX_PROGRAM_LENGTH];
  while(fscanf(input, "%s", thisToken) != EOF)
  {
    if(thisToken != NULL)
    {
      symbolChecking(thisToken);
    }
  }

  // Don't bother with fclose here because we'll need the file pointer
  // again shortly, same with output. This keeps us from having to do
  // failure handling every time. 

  return;

}
